<?php

require_once 'Zend/Registry.php';
require_once 'LayeredContent/UrlHandler.php';
require_once 'LayeredContent/ResourceHandler.php';
require_once 'LayeredContent/CacheHandler.php';
require_once 'LayeredContent/DataStore.php';
require_once 'LayeredContent/TemplateHandler.php';


/**
 * If Layered Content is being used stand-alone without a web framework, simply
 * create a new instance and call run(). If using in conjunction with a web
 * framework see the rest of the methods.
 */
class LayeredContent
{
	/**
	 * The system configuration settings.
	 *
	 * @var Zend_Config
	 */
	protected $config;
	
	/**
	 * URL Handler to handle how we create and use our URLs. This could be
	 * replaced by your own class with the same methods.
	 *
	 * @var LayeredContent_UrlHandler
	 */
	protected $urlHandler;
	
	/**
	 * URL Handler to handle how we create and use our URLs. This could be
	 * replaced by your own class with the same methods.
	 *
	 * @var LayeredContent_ResourceHandler
	 */
	protected $resourceHandler;
	
	/**
	 * URL Handler to handle how we create and use our URLs. This could be
	 * replaced by your own class with the same methods.
	 *
	 * @var LayeredContent_CacheHandler
	 */
	protected $cacheHandler;
	
	/**
	 * URL Handler to handle how we create and use our URLs. This could be
	 * replaced by your own class with the same methods.
	 *
	 * @var LayeredContent_DataStore
	 */
	protected $dataStore;
	
	/**
	 * Singleton instance
	 *
	 * Marked only as protected to allow extension of the class. To extend,
	 * simply override {@link getInstance()}.
	 *
	 * @var LayeredContent
	 */
	protected static $_instance = null;

	/**
	 * Singleton instance
	 *
	 * @return LayeredContent
	 */
	public static function getInstance ()
	{
		if (null === self::$_instance) {
			self::$_instance = new self();
		}
		
		return self::$_instance;
	}

	
	/**
	 * Constructor
	 */
	public function __construct ()
	{
		$this->loadConfig();
		$this->setHandler('urlHandler', new LayeredContent_UrlHandler());
		$this->setHandler('resourceHandler', new LayeredContent_ResourceHandler());
		$this->setHandler('cacheHandler', new LayeredContent_CacheHandler());
		$this->setHandler('dataStore', new LayeredContent_DataStore());
		$this->setHandler('templateHandler', new LayeredContent_TemplateHandler());
		$this->init();
	}

	protected function init ()
	{

	}

	/**
	 * Allow handlers to be overridden and custom handler's to be used.
	 */
	public function setHandler ($name, LayeredContent_Handler $handler)
	{
		$handlers = array('urlHandler' , 'resourceHandler' , 'cacheHandler' , 'dataStore' , 'templateHandler');
		
		if (! in_array($name, $handlers)) {
			throw new Exception("Handler name '$name' not valid.");
		}
		
		$this->$name = $handler;
		$this->initHandler($handler);
	}

	/**
	 * @return LayeredContent_Handler
	 */
	public function getHandler ($name)
	{
		$handlers = array('urlHandler' , 'resourceHandler' , 'cacheHandler' , 'dataStore' , 'templateHandler');
		
		if (! in_array($name, $handlers)) {
			throw new Exception("Handler name '$name' not valid.");
		}
		
		return $this->$name;
	}

	
	/**
	 * This will process the request, find the specified resource, and return
	 * the data. It will also handle 404 Page Not Found errors when there is no
	 * resource found for this request.
	 */
	public function run ()
	{
		$action = $this->getAction();
		
		// first check if the cache exists, if it does, send it to the browser
		if ($action == 'view' && $this->hasCache()) {
			if (! $this->isCachedByBrowser())
				$this->getCache(true);
			return;
		}
		
		$resource = $this->resourceHandler->getResource();
		// are we pointing to a valid resource, or a list? If 
	//$this->resourceHandler->
	

	//$resource = $this->dataStore->getResource($this->urlHandler->getFullPath());
	//var_dump($resource);
	//$this->process();
	}

	/**
	 * Use before init() if the current URL isn't the one that this resource
	 * should be stored under.
	 *
	 * @param string $url The format should be /my/url, mydomain.com/my/url, or
	 * http://mydomain.com/my/url
	 */
	public function setURL ($url)
	{
		$this->urlHandler->setURL($url);
	}

	/**
	 * Returns the currently set URL Layered Content will be using. If none has
	 * been set then it will be the current URL in the browser. Or, if a partial
	 * URL is passed in this will return the full URL for that. 
	 * 
	 * @param string An optional URL which will be parsed and returned as a full
	 * URL. If none is passed in the default is to return the currently set URL.
	 * @return string The full URL (e.g. http://mydomain.com/my/url)
	 */
	public function getURL ($url = null)
	{
		return $this->urlHandler->getURL($url);
	}

	/**
	 * The action this request wants to perform. Should be list, view, create,
	 * update, or delete.
	 */
	public function getAction ()
	{
		return $this->urlHandler->action;
	}

	
	/**
	 * Initialize a request with Layered Content so that data for the current
	 * location may be stored and used. This method is to be used in conjunction
	 * with existing code and frameworks. Use the setURL method if the current
	 * URL isn't where you'd like to pull data from. initialize() should be
	 * called before getData(), getTemplate(), etc.
	 * 
	 * Only call this on resources that represent the data you are pulling. If
	 * you are simply getting the data from another resource, call setURL() and
	 * getData() without calling initialize().
	 */
	public function initialize ()
	{	

	// use this to determine what fields will be available for this resource
	

	// look for the right query strings to see if we should be returning
	//data to the browser instead of progromatically
	

	// allow for other options to be set for this resource
	}

	
	/**
	 * Determines if there is an existing cache for this resource. Allows
	 * systems that don't utilize caching to tie into Layered Content's caching.
	 * By calling this method to check for a cache, it also initializes Layered
	 * Content to create a cache for this resource by capturing the ouput and
	 * saving the cache. Then the next request may use that cache.
	 * 
	 * @return boolean Whether or not the resource has a valid cache. If it
	 * does, get and use the cache with getCache()
	 */
	public function hasCache ()
	{
		return $this->cacheHandler->hasCache();
	}

	/**
	 * If there is an existing cache for this resource, checks the date modified
	 * and the browser's headers that were sent to determine if the browser has 
	 * a current cache on the client side. If so, sends a 304 Not Modified
	 * header and no cache is needed to be sent. In addition to being faster
	 * this also saves bandwith. This method will also send the Last-Modified
	 * and ETag headers to the browser so that next time the browser will have
	 * it cached.
	 * 
	 * @return boolean Whether or not the resource has a valid cache. If it
	 * does, get and use the cache with getCache()
	 */
	public function isCachedByBrowser ()
	{
		return $this->cacheHandler->isCachedByBrowser();
	}

	/**
	 * Returns the cache as a string. This may then be sent to the browser. You
	 * may optionally specify to send the cache directly to the browser and exit
	 * the script immediately with the $final parameter.
	 * 
	 * @param boolean $final This indicates that instead of returning the cache
	 * for you to send to the browser, let Layered Content send it to the
	 * browser and exit the running script. The default is false.
	 * @return void|string The string data for the cache if $final was not set
	 * to true.
	 */
	public function getCache ($final = false)
	{
		$contents = $this->cacheHandler->getCache();
		if ($final)
			die($contents);
		
		return $contents;
	}

	/**
	 * Deletes any cache which may exist for this resource. If none exists it
	 * will simply return. Use this when you are using the caching feature in
	 * conjunction with your own data and queries to a database. If you are only
	 * using the data from Layered Content you don't need to worry about
	 * deleting the cache as Layered Content takes care of all that itself.
	 */
	public function deleteCache ()
	{
		$this->cacheHandler->deleteCache();
	}

	/**
	 * Returns an associative array of the data this resource holds. This data
	 * is specified in init() and stored for this resource. It is also editable
	 * through Layered Content's APIs. This can be added to the data going into
	 * your templates.
	 *
	 * @return array An associative array of the data belonging to this
	 * resource.
	 */
	public function getData ()
	{
		$data = $this->dataStore->getResource();
		return $data;
	}

	/**
	 * If using Layered Content's templating system you can add your own data
	 * for use and display in the template. Note that this data cannot be
	 * modified or saved from Layered Content's API.
	 *
	 * @param array $data Associative array of data you'd like to be added to
	 * the template.
	 */
	public function addData ($data)
	{

	}

	/**
	 * Allows you to use Layered Content's templating system and caching while
	 * still being able to pass in your own data. If you want Layered Content to
	 * be able to change the template, pass in null for the first parameter.
	 * 
	 * Only custom data needs to be passed into the $data parameter. This method
	 * will automatically add the content from the getData() method. 
	 * 
	 * @param string $template The template name to be used
	 * @param array $data The data to be passed into the template
	 * @return string The ouput from the template
	 */
	public function runTemplate ($template = null, $data = null)
	{
		return $template;
	}

	/**
	 * Performs a search on the content/data in the system. Specifying a URL
	 * will limit the search to the specified resource and anything under it
	 * (e.g. /blog or /store/products).
	 *
	 * @param string $query The query typed in by a user
	 * @param string $url The base URL from which to search. A value of null
	 * searches the entire site. The default is null.
	 * @return Returns an array of hit objects which contain the URL, the score
	 * in terms of how close a match, and is ordered by the highest score,
	 * descending.
	 */
	public function search ($query, $url = null)
	{

	}

	
	/**
	 * Saves data for current resource (or the one that has been set with
	 * setURL). 
	 * 
	 * @param array $data An associative array with the data to be saved to the
	 * resource. This is the data that is returned with getData() which is used
	 * in the resource.
	 */
	public function saveData ($data)
	{

	}

	/**
	 * Saves new information for the current resource. If the URL is changing or
	 * if you are using the templates and the template changes, use this method
	 * to save those. Note, if you are saving a new URL you should redirect to
	 * the new URL after this save.
	 *
	 * @param string $url The new URL of the resource being saved.
	 */
	public function saveResource ($url)
	{

	}

	/**
	 * Issue a redirect code, either a permanent (the default) or a temporary
	 * redirect. 
	 *
	 * @param string $url The URL to redirect to.
	 * @param boolean $permanent Whether to use a permanent redirect or not. The
	 * default is true.
	 */
	public function redirect ($url, $permanent = true)
	{
		if ($permanent) {
			header('HTTP/1.1 301 Moved Permanently');
		} else {
			header('HTTP/1.1 307 Temporary Redirect');
		}
		
		// make sure this is an absolute path, not relative
		$url = $this->getURL($url);
		
		header("Location: $url");
	}

	/**
	 * Sends a 404 status to the server along with the default 404 template.
	 * Also outputs the reason why a 404 was found if we're in development mode.
	 * 
	 * @param string $message The error message to be sent to the resource when
	 * development mode is on.
	 */
	public function send404 ($message = null)
	{
		header("HTTP/1.1 404 Page Not Found");
		// TODO use the templating system to handle this corretly
		readfile(TEMPLATES . "404.html");
		
		if ($message && $this->config->debug) {
			echo "<font style='font-weight:bold;color:red'>$message</font>";
		}
		exit();
	}

	/**
	 * Sets up and initializes a layered content handler so that they all have
	 * references to each other. Will also be called on $this as its own handler
	 *
	 * @param LayeredContent_Handler $handler
	 */
	protected function initHandler (LayeredContent_Handler $handler)
	{
		$handler->config = $this->config;
		$handler->layeredContent = $this;
		$handler->init();
	}

	
	protected function loadConfig ()
	{
		// Load Configuration
		require_once 'Zend/Config/Ini.php';
		$this->config = new Zend_Config_Ini(LAYERED_PATH . '/config/main.ini', null, true);
		$this->config->resources = new Zend_Config_Ini(LAYERED_PATH . '/config/resources.ini');
	}

	
	protected function process ()
	{
		// if the cache doesn't exist, load the system up
		require_once 'Zend/Db.php';
		require_once 'Zend/Db/Table/Abstract.php';
		require_once 'Zend/Controller/Front.php';
		
		$config = Zend_Registry::get('config');
		
		// Register the database connection (won't connect until we use it)
		$db = Zend_Db::factory($config->database->adapter, $config->database);
		Zend_Registry::set('db', $db);
		Zend_Db_Table_Abstract::setDefaultAdapter($db);
		

		// Set up the front controller and dispatch
		$front = Zend_Controller_Front::getInstance();
		$front->getRouter()->addConfig($config, 'routes');
		$front->throwExceptions($config->debug);
		$front->setControllerDirectory(LAYERED_PATH . 'application/controllers');
		$front->returnResponse(true);
		return $front->dispatch();
	}

}